
public class GameLogicTeamRoDK implements IGameLogic {
	private int x = 0; 				// number of columns to play
    private int y = 0; 				// number of rows to play
    private int playerID; 			// player Id of this class
    private int[][] gameBoard;		// board to play
    private static final int maxVal = 100000000;

    // Minimum value discovered after running tests as being the optimum to run fast and well
    // If it is increased the quality of the game increases, but the speed of the movements increase (over 10 sec)
    private int maxLevel = 7;
    
    public GameLogicTeamRoDK() {
    }
	
    // public functions
    //
    public void initializeGame(int x, int y, int playerID) {
        this.x = x;
        this.y = y;
        this.playerID = playerID;
        this.gameBoard = new int[x][y];
    }
	
    public Winner gameFinished() {
        return checkGameIsFinished(gameBoard);
    }

    public void insertCoin(int column, int playerID) 
    {
    	internalInsertCoin(column, playerID);
    }

    // consider the early wining position, and then early lose position and only after start minimax algorithm
	public int decideNextMove() {
		int column = 0;

		if (playerID == 1) {
			column = getForcedMove(this.maxVal);
			if (column == -1) {
				column = getForcedMove(-this.maxVal);
				if (column == -1)
					column = getMaxColumn();
			}
		} else {
			column = getForcedMove(-this.maxVal);
			if (column == -1) {
				column = getForcedMove(this.maxVal);
				if (column == -1)
					column = getMinColumn();
			}
		}
		return column;
	}
	
	// return early winning position (in case there is needed only one piece win, being already 3 in line)
	//
	public int getForcedMove(int winningValue)
	{
		for (int column = 0; column < this.x; column++) {
			if (internalInsertCoin(column, winningValue == this.maxVal ? 1 : 2) < 0) 	// simulate movement
				continue;
			int currentValue = evaluateBoard(this.gameBoard);
			removeCoin(column); 							// clean the simulated movement
			if (currentValue == winningValue)
				return column;
		}

		return -1;
	}

    // function used for tests, to enter the movements in a friendly way (ex: a1, b2 - letter is the column)
	//
	public void insertCoin(String movment, int playerID) 
	{
	    if (movment.length() != 2)
			throw new IllegalArgumentException("Selected movement is illegal " + movment + " !");	
		
		int column = (movment.toLowerCase().charAt(0) - "a".charAt(0));
	  	int row = this.y - Integer.parseInt(movment.substring(1, 2));
	
		if (row != internalInsertCoin(column, playerID))
			throw new IllegalArgumentException("Movment could not be made for column and row selected !");
	}

	// function used to add a coin
	//
	private int internalInsertCoin(int column, int playerID) {
		if (column < 0 || column >= this.x)
			throw new IllegalArgumentException(
					"Movment could not be made for selected column (" + column + ") !");

		int row = getFirstFreeRow(column);
		if (isValidCoord(column, row) && this.gameBoard[column][row] == 0)
			this.gameBoard[column][row] = playerID;

		return row;
	}
    
    // used for simulation only, keeping only one array (real + simulation)
	//
	private int removeCoin(int column, int[][] insertBoard) {
    	if (column < 0 || column >= this.x)
    		throw new IllegalArgumentException("Movment could not be made for selected column (" + column + ") !");

		int row = getFirstFreeRow(column);
		if (isValidCoord(column, row + 1) && insertBoard[column][row + 1] != 0)
			insertBoard[column][row + 1] = 0;
		else
			throw new IllegalArgumentException("Remove movment could not be made for column selected !");
			
		return row + 1;
	}

	// used for simulation only, keeping only one array (real + simulation)
	//
	private int removeCoin(int column) {
		return removeCoin(column, this.gameBoard);
	}
	
	// check if game is finished passing through entire board and consider all viable situations
	//
   	public Winner checkGameIsFinished(int[][] board)
    {
        int row;
		int column;
		int coinsMultiplied;
    	
    	// Check first for TIE
		for (column = 0; column < x; column++)
		{
			if (board[column][0] == 0) 
				break;
			
			if (column == x-1) 
				return Winner.TIE;
		}		
    	
		// Horizontal check
		row = y - 1;
		coinsMultiplied = 0;
		while (row >= 0) {
			column = 0;
			while (column < x - 3) {
				coinsMultiplied = board[column][row] * board[column + 1][row]
						* board[column + 2][row] * board[column + 3][row];
				if (coinsMultiplied == 1)
					return Winner.PLAYER1;
				if (coinsMultiplied == 16)
					return Winner.PLAYER2;
				column++;
			}
			row--;
		}

		// Vertical check
		column = 0;
		coinsMultiplied = 0;
		while (column < x) {
			row = y - 1;
			while (row > 2) {
				coinsMultiplied = board[column][row] * board[column][row - 1]
						* board[column][row - 2] * board[column][row - 3];
				if (coinsMultiplied == 0)
					break;
				if (coinsMultiplied == 1)
					return Winner.PLAYER1;
				if (coinsMultiplied == 16)
					return Winner.PLAYER2;
				row--;
			}
			column++;
		}

		// Diagonal from bottom left
		coinsMultiplied = 0;
		for (row = 3; row < y; row++) {
			column = 0;
			while (column < x - 3) {
				coinsMultiplied = board[column][row]
						* board[column + 1][row - 1]
						* board[column + 2][row - 2]
						* board[column + 3][row - 3];

				if (coinsMultiplied == 1)
					return Winner.PLAYER1;
				if (coinsMultiplied == 16)
					return Winner.PLAYER2;

				column++;
			}

		}

		// the other diagonal
		coinsMultiplied = 0;
		for (row = 3; row < y; row++) {
			column = 3;
			while (column < x) {
				coinsMultiplied = board[column][row]
						* board[column - 1][row - 1]
						* board[column - 2][row - 2]
						* board[column - 3][row - 3];

				if (coinsMultiplied == 1)
					return Winner.PLAYER1;
				if (coinsMultiplied == 16)
					return Winner.PLAYER2;

				column++;
			}
		}

		return Winner.NOT_FINISHED;
	}
    
   	// Evaluate board - function used inside the minimax algorithm for level 0
   	// Function pass through all viable lines and run evaluate function; in case it discovers an early winning it stops
   	//
	public int evaluateBoard(int[][] evaluationBoard)
    {
    	int evaluationVal = 0;
    	int intermediateVal = 0;
    	int[] line = new int[4];
    	
    	// Evaluate horizontally
    	for (int column = 0; column < x - 3; column ++)
    	{
    		for (int row = 0; row < y; row ++)
    		{
    			line[0] = evaluationBoard[column][row];
    			line[1] = evaluationBoard[column+1][row];
    			line[2] = evaluationBoard[column+2][row];
    			line[3] = evaluationBoard[column+3][row];
    			intermediateVal = evaluateLine(line,row);
    			if (intermediateVal == this.maxVal || intermediateVal == -this.maxVal)
    				return intermediateVal;
                       
    			evaluationVal = evaluationVal + intermediateVal;		
    		}
    	}

    	// Evaluate vertically
    	for (int row = 0; row < y - 3; row ++)
    	{
    		for (int column = 0; column < x; column ++)
    		{
    			line[0] = evaluationBoard[column][row];
    			line[1] = evaluationBoard[column][row+1];
    			line[2] = evaluationBoard[column][row+2];
    			line[3] = evaluationBoard[column][row+3];
    			intermediateVal = evaluateLine(line,1);
    			if (intermediateVal == this.maxVal || intermediateVal == -this.maxVal)
    				return intermediateVal;
    			
    			evaluationVal = evaluationVal + intermediateVal;		
    		}
    	}

    	// Evaluate diagonally from top left
       	for (int column = 0; column < x - 3; column ++)
    	{
    		for (int row = 0; row < y - 3; row ++)
    		{
    			line[0] = evaluationBoard[column][row];
    			line[1] = evaluationBoard[column+1][row+1];
    			line[2] = evaluationBoard[column+2][row+2];
    			line[3] = evaluationBoard[column+3][row+3];
    			intermediateVal = evaluateLine(line,1);
    			if (intermediateVal == this.maxVal || intermediateVal == -this.maxVal)
    				return intermediateVal;
    			
    			evaluationVal = evaluationVal + intermediateVal;		
    		}
    	}

    	// Evaluate diagonally from bottom left
       	for (int column = 0; column < x - 3; column ++)
    	{
    		for (int row = 3; row < y; row ++)
    		{
    			line[0] = evaluationBoard[column][row];
    			line[1] = evaluationBoard[column+1][row-1];
    			line[2] = evaluationBoard[column+2][row-2];
    			line[3] = evaluationBoard[column+3][row-3];
    			intermediateVal = evaluateLine(line,1);
    			if (intermediateVal == this.maxVal || intermediateVal == -this.maxVal)
    				return intermediateVal;
    			
    			evaluationVal = evaluationVal + intermediateVal;		
    		}
    	}
       	
    	return evaluationVal;
    }
    
	// Evaluation function, used in mini max algorithm - it is called for each 4 elements
	// It consider only lines that theoretically could lead to a winning or lose
	//
	private int evaluateLine(int[] line,int row)
    {
        int threeInALine = 20 * row;
    	int twoInALine = 1 * row;
    	int returnValue = 0;

    	switch (line[0] | line[1] | line[2] | line[3])
    	{
    	case 1: // the lines contains only 1 item types
    		switch (line[0] + line[1] + line[2] + line[3])
    		{
    		case 2: returnValue = twoInALine; break;
    		case 3: returnValue = threeInALine; break;
    		case 4: returnValue = maxVal; break;
    		};
    		break;
    	case 2: // the lines contains only 2 item types
    		switch (line[0] + line[1] + line[2] + line[3])
    		{
    		case 4: returnValue = -twoInALine; break;
    		case 6: returnValue = -threeInALine; break;
    		case 8: returnValue = -maxVal; break;
    		}
    		break;
    	}
        
    	return returnValue;
    }
    
    // check if given coordinates for a given cell are valid
    //
	private Boolean isValidCoord(int x, int y) {
		return x >= 0 && x < this.x && y >= 0 && y < this.y;
	}
    
    // find the first free row on a selected column (where to put the piece)
    // return -1, in case the row is full
    //
	private int getFirstFreeRow(int selectedColumn) {
		if (selectedColumn < 0 || selectedColumn >= this.x)
			return -1;

		int row = this.y - 1;
		while (row >= 0 && this.gameBoard[selectedColumn][row] != 0)
			row--;

		return row;
	}

	// function called once it was detected an early winning (for Min or Max) inside the minimax algorithm
	//
	private int evaluatePlayer(Winner player) {
		if (player == Winner.NOT_FINISHED)
			throw new IllegalArgumentException(
					"Evaluation could not be run for this option !");

		switch (player) {
		case PLAYER1:
			return maxVal;
		case PLAYER2:
			return -1 * maxVal;
		case TIE:
			return 0;
		}

		return 0;
	}
	
    // runs the minimax algorithm with alpha beta cut for player 1
    // playerId 1 plays always max
    //
	public int getMaxColumn() {
		return maxMove2(this.maxLevel, new Pair("max")).column;
	}
    
    // get the maximum value, taking into account the Beta pruning
    //
    // returns: the value; if win maxValue
    public Pair maxMove2(int level, Pair parentMin)
    {
    	 // it runs an early evaluation function to discover if the game is already won, 
    	 // and to not continue the evaluations
    	 Winner player = checkGameIsFinished(this.gameBoard);
    	 if (player != Winner.NOT_FINISHED)
    	   return new Pair(evaluatePlayer(player));
    	
    	 if(level == 0) 
    		return new Pair(evaluateBoard(this.gameBoard));
    	
    	// encourage to start with middle & 
    	Pair maxSelection = new Pair(-maxVal*2); 
    	for (int column = 0; column < this.x; column++)
		{
			if (internalInsertCoin(column, 1) < 0)			// simulate movement
				continue; // column is full
			
			Pair result = minMove2(level - 1, maxSelection); 		 
			removeCoin(column);										// clean the simulated movement
			
			Boolean exitCriteria = result.equal("max") || result.greater(parentMin); // no need to go further, because the max will be greater than the minimum already found (Beta pruning)
																					 // OR no need to go further, win, return maximum value, no matter what	
			if (exitCriteria 
					|| result.greater(maxSelection)
					   // Prioritize central columns
					|| (result.equal(maxSelection) && Math.abs(column - (this.x -1) / 2) < Math.abs(maxSelection.column - (x-1) / 2))) 
			{
				maxSelection.set(column, result.value);
				if (exitCriteria)
					break; 
            }
		}
    	
    	return maxSelection;
    }

    // runs the minimax algorithm with alpha beta cut for player 2
    // playerId 2 plays min
    //
	public int getMinColumn() {
		return minMove2(this.maxLevel, new Pair("min")).column;
	}
    
    // get the minimum value, taking into account the Alpha pruning
    //
    // returns: the value; if win maxValue
    public Pair minMove2(int level, Pair parentMax)
    {
	   	 // it runs an early evaluation function to discover if the game is already won, 
	   	 // and to not continue the evaluations
    	 Winner player = checkGameIsFinished(this.gameBoard);
    	 if (player != Winner.NOT_FINISHED)
    	 	return new Pair(evaluatePlayer(player));
    	
    	if (level == 0) 
    		return new Pair(evaluateBoard(this.gameBoard));
    	
    	Pair minSelection = new Pair(maxVal*2);
		for (int column = 0; column < this.x; column++)
		{
			if (internalInsertCoin(column, 2) < 0)	// simulate movement
				continue; // column is full
			
			Pair result = maxMove2(level - 1, minSelection);

			removeCoin(column); 	// clean the simulated movement

			Boolean exitCriteria = result.equal("min") || result.lower(parentMax);  // no need to go further, when it is equal with minValue since opponent wins
																					// no need to go further, because the minimum will be less than the max already found (alpha pruning)
			if (exitCriteria || result.lower(minSelection)
							   // Prioritize central columns
							 || (result.equal(minSelection) && Math.abs(column - (this.x -1) / 2) < Math.abs(minSelection.column - (x-1) / 2))) 
			{
				minSelection.set(column, result.value);
				if (exitCriteria)
					break; 
			}
		}
		
		return minSelection;
    }
    
	// helper function, used in tests (function made public to be accesible by JUnit)
	//
	public int getNumberOfCoins(int playerID) {
		int numberOfCoins = 0;
		for (int column = 0; column < this.x; column++)
			for (int row = 0; row < this.y; row++) {
				numberOfCoins += this.gameBoard[column][row] == playerID ? 1
						: 0;
			}

		return numberOfCoins;
	}

	// helper function, used in tests (function made public to be accesible by JUnit)
	//
	public void PrintGameBoard(int level, String p) {
		System.out.println("GameBoard evaluation at level " + level + " for " + p);
		for (int row = 0; row < this.y; row++) {
			for (int col = 0; col < this.x; col++) {
				System.out.print(this.gameBoard[col][row] + " ");
			}
			System.out.println();
		}
		System.out.println();
	}
    
	// helper function, used in tests (function made public to be accesible by JUnit)
	//
    public Boolean isGameFinished() {
        return checkGameIsFinished(gameBoard) != Winner.NOT_FINISHED;
    }
  
    public int getX() {
        return x;
    }

    public int getY() {
        return y;
    }

    public static int getMaxval() {
		return maxVal;
	}
	
    public int[][] getGameBoard() {
        return gameBoard;
    }

    public void setGameBoard(int[][] gameBoard) {
        this.gameBoard = gameBoard;
    }
}



